/*
//
//              INTEL CORPORATION PROPRIETARY INFORMATION
//  This software is supplied under the terms of a license  agreement or
//  nondisclosure agreement with Intel Corporation and may not be copied
//  or disclosed except in  accordance  with the terms of that agreement.
//    Copyright (c) 2007-2008 Intel Corporation. All Rights Reserved.
//
//
*/

#include "umc_defs.h"
#if defined(UMC_ENABLE_AVS_VIDEO_ENCODER)

#include "umc_avs_enc_stream_copier.h"
#include "umc_avs_enc_bit_stream.h"
#include "umc_avs_pic.h"

namespace UMC
{

AVSStreamCopier::AVSStreamCopier(void)
{
    memset(&m_seqHeader, 0, sizeof(AVS_SEQUENCE_HEADER));

    m_numFrames = 0;
    m_seqHeaderPeriod = 0;
    m_lastSeqHeaderNum = 0;

} // AVSStreamCopier::AVSStreamCopier(void)

AVSStreamCopier::~AVSStreamCopier(void)
{
    Release();

} // AVSStreamCopier::~AVSStreamCopier(void)

void AVSStreamCopier::Release(void)
{
    memset(&m_seqHeader, 0, sizeof(AVS_SEQUENCE_HEADER));

    m_numFrames = 0;
    m_seqHeaderPeriod = 0;
    m_lastSeqHeaderNum = 0;

} // void AVSStreamCopier::Release(void)

Status AVSStreamCopier::Init(void)
{
    // release the object before initialization
    Release();

    return UMC_OK;

} // Status AVSStreamCopier::Init(void)

static
Ipp8u *AlignPointer(Ipp8u *pbDst)
{
    while ((pbDst - (Ipp8u *) 0) & 3)
    {
        *pbDst = 0;
        pbDst += 1;
    }

    return pbDst;

} // Ipp8u *AlignPointer(Ipp8u *pbDst)

static
bool AreHeaderEqual(AVS_SEQUENCE_HEADER *pOne, AVS_SEQUENCE_HEADER *pTwo)
{
    if ((pOne->profile_id != pTwo->profile_id) ||
        (pOne->level_id != pTwo->level_id) ||
        (pOne->progressive_sequence != pTwo->progressive_sequence) ||
        (pOne->horizontal_size != pTwo->horizontal_size) ||
        (pOne->vertical_size != pTwo->vertical_size) ||
        (pOne->chroma_format != pTwo->chroma_format) ||
        (pOne->sample_precission != pTwo->sample_precission) ||
        (pOne->aspect_ratio != pTwo->aspect_ratio) ||
        (pOne->frame_rate != pTwo->frame_rate) ||
        (pOne->bit_rate_lower != pTwo->bit_rate_lower) ||
        (pOne->bit_rate_upper != pTwo->bit_rate_upper) ||
        (pOne->low_delay != pTwo->low_delay) ||
        (pOne->bbv_buffer_size != pTwo->bbv_buffer_size))
    {
        return false;
    }

    return true;

} // bool AreHeaderEqual(AVS_SEQUENCE_HEADER *pOne, AVS_SEQUENCE_HEADER *pTwo)

class AVSDwordReader
{
public:
    AVSDwordReader(const Ipp32u *pSrc, size_t srcSize)
    {
        m_pSrc = pSrc;
        m_pSrcEnd = pSrc + srcSize;

        m_nBits = 0;
        m_numBits = 0;
    }

    Ipp32u GetBits(Ipp32s numBits)
    {
        Ipp32u bits;

        // number of ready bits is not enough
        if (m_numBits < numBits)
        {
            bits = m_nBits << (numBits - m_numBits);
            m_nBits = *m_pSrc;
            m_pSrc += 1;
            bits |= (m_nBits >> (32 - (numBits - m_numBits)));

            m_numBits += 32 - numBits;
        }
        else if (m_numBits == numBits)
        {
            bits = m_nBits;

            m_nBits = *m_pSrc;
            m_pSrc += 1;
            m_numBits = 32;
        }
        // the amount of ready bits is quite big
        else
        {
            m_numBits -= numBits;
            bits = m_nBits >> m_numBits;
        }

        return (bits & ~(-1 << numBits));
    }

    bool IsThereMoreData(void)
    {
        if (m_pSrcEnd > m_pSrc)
            return true;
        if (m_pSrcEnd == m_pSrc)
        {
            if (m_numBits)
                return true;
        }

        return false;
    }

protected:

    const Ipp32u *m_pSrc;                                       // (Ipp32u *) the current reading position
    const Ipp32u *m_pSrcEnd;                                    // (Ipp32u *) the end of the source buffer

    Ipp32u m_nBits;                                             // (Ipp32u) cached bits
    Ipp32s m_numBits;                                           // (Ipp32s) number of free bits in the cache
};

static
Ipp8u *SwapAndAddPreventionBits(Ipp8u *pbDst, const Ipp32u *pSrc, size_t srcSize)
{
    AVSDwordReader src(pSrc, srcSize);

    // copy start code
    pbDst[0] = (Ipp8u) src.GetBits(8);
    pbDst[1] = (Ipp8u) src.GetBits(8);
    pbDst[2] = (Ipp8u) src.GetBits(8);
    pbDst[3] = (Ipp8u) src.GetBits(8);
    pbDst += 4;

    // copy source add prevention bits
    while (src.IsThereMoreData())
    {
        Ipp32u bits = src.GetBits(8);

        // we found the first prevention pattern
        if ((0 == pbDst[-1]) &
            (0 == pbDst[-2]) &
            (0 == (bits & -4)))
        {
            // set the 0000 0010 bit sequence
            *pbDst = 2;
            pbDst += 1;
            bits = (bits << 6) | src.GetBits(6);
        }

        // put byte into the destination stream
        *pbDst = (Ipp8u) bits;
        pbDst += 1;
    }

    return pbDst;

} // Ipp8u *SwapAndAddPreventionBits(Ipp8u *pbDst, const Ipp32u *pSrc, size_t srcSize)

Status AVSStreamCopier::CopyFrame(void)
{
    Ipp8u *pbDst, *pbDstEnd;
    AVSFrame *pFrame = m_cpyCtx.m_pSrc;

    // set pointers
    pbDst = (Ipp8u *) m_cpyCtx.m_pDst->GetBufferPointer();
    pbDstEnd = pbDst + m_cpyCtx.m_pDst->GetBufferSize();

    // align the pointer
    pbDst = AlignPointer(pbDst);
    if (pbDst >= pbDstEnd)
        return UMC_ERR_NOT_ENOUGH_BUFFER;
    //{
    //    char cStr[256];
    //    sprintf(cStr, "Frame num was copied[% 5d], Frame type is %s \n",
    //            pFrame->m_frameNum,
    //            (AVS_P_PICTURE == pFrame->m_picHeader.PictureType) ? ("P") :
    //            ((AVS_B_PICTURE == pFrame->m_picHeader.PictureType) ? ("B") : ("I")) );
    //    OutputDebugString(cStr);
    //}

    //
    // first of all encode sequence header
    //
    if (AVS_I_PICTURE == pFrame->m_picHeader.PictureType)
    {
        bool bEqual = AreHeaderEqual(&m_seqHeader, &(m_cpyCtx.m_pSrc->m_seqHeader));

        if ((false == bEqual) ||
            (m_numFrames - m_lastSeqHeaderNum >= m_seqHeaderPeriod))
        {
            Status umcRes;
            umcRes = EncodeAVSSequenceHeader(&pbDst,
                                             pbDstEnd - pbDst,
                                             &(m_cpyCtx.m_pSrc->m_seqHeader));
            if (UMC_OK != umcRes)
                return umcRes;

            // save the number of the latest frame with sequence header
            m_lastSeqHeaderNum = m_numFrames;
        }

        // save sequence header parameters
        if (false == bEqual)
        {
            m_seqHeader = m_cpyCtx.m_pSrc->m_seqHeader;
            m_seqHeaderPeriod = (Ipp32u) m_cpyCtx.m_pSrc->m_seqHeader.frame_rate;
        }
    }
    // align the pointer
    pbDst = AlignPointer(pbDst);
    if (pbDst >= pbDstEnd)
        return UMC_ERR_NOT_ENOUGH_BUFFER;

    //
    // encode picture header
    //
    {
        Ipp32u headerBuf[16];
        AVS_BIT_STREAM_CONTEXT bitStream;

        // initialize the bit stream
        InitializeEncBitStream(&bitStream, headerBuf, sizeof(headerBuf));

        // encode bit stream
        EncodePicHeader(&bitStream, &m_seqHeader, &(m_cpyCtx.m_pSrc->m_picHeader));
        FlushBitStream(&bitStream);
        bitStream.dst[0] = 0;

        pbDst = SwapAndAddPreventionBits(pbDst, headerBuf, bitStream.dst - headerBuf);
    }
    // align the pointer
    pbDst = AlignPointer(pbDst);
    if (pbDst >= pbDstEnd)
        return UMC_ERR_NOT_ENOUGH_BUFFER;

    //
    // copy slices
    //
    {
        AVSListElement<AVSSlice> *pSlice;
        AVSListElement<AVSMemory> *pMemory;

        // get the slice queue and memory queue
        if (pFrame->m_picHeader.picture_structure)
        {
            pSlice = pFrame->m_SlicesSpent.GetHead();
        }
        else
        {
            pSlice = pFrame->GetPicture(AVS_UPPER_FIELD)->m_SlicesSpent.GetHead();
        }
        pMemory = pFrame->m_MemoryPieces.GetHead();

        // run though memory list
        while (pMemory)
        {
            AVS_BIT_STREAM_CONTEXT bitStream;
            Ipp32u *pSrc = pMemory->GetBuffer();

            if (NULL == pSlice)
            {
                // get the next field
                pSlice = pFrame->GetPicture(AVS_LOWER_FIELD)->m_SlicesSpent.GetHead();
            }

            // get flush bit stream
            bitStream = pSlice->m_decCtx.m_stream;
            FlushBitStream(&bitStream);
            bitStream.dst[0] = 0;

            // copy it to destination buffer
            pbDst = SwapAndAddPreventionBits(pbDst,
                                             pSrc,
                                             bitStream.dst - pSrc);

            // get next piece of memory
            pMemory = pMemory->GetNext();
            // get next slice
            pSlice = pSlice->GetNext();
        }
    }

    // set the frame into 'shown' state
    m_cpyCtx.m_pSrc->SetShown();

    // increment number of processed frames
    m_numFrames += 1;

    // set the number of valid bytes
    m_cpyCtx.m_pDst->SetDataSize(pbDst - (Ipp8u *) m_cpyCtx.m_pDst->GetBufferPointer());

    return UMC_OK;

} // Status AVSStreamCopier::CopyFrame(void)

} // namespace UMC

#endif // defined(UMC_ENABLE_AVS_VIDEO_ENCODER)
